package cn.wangdh.domain.strategy.service.armory;

import cn.wangdh.domain.strategy.model.entity.StrategyAwardEntity;
import cn.wangdh.domain.strategy.model.entity.StrategyEntity;
import cn.wangdh.domain.strategy.model.entity.StrategyRuleEntity;
import cn.wangdh.domain.strategy.repository.IStrategyRepository;
import cn.wangdh.types.common.Constants;
import cn.wangdh.types.enums.ResponseCode;
import cn.wangdh.types.exception.AppException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.security.SecureRandom;
import java.util.*;

/**
 * @author WDH
 * @description 策略装配库 负责初始化策略计算
 * @date 2024年09月09日 13:27
 */
@Slf4j
@Service
public class StrategyArmoryDispatch implements IStrategyArmory, IStrategyDispatch {

    @Resource
    private IStrategyRepository repository;

    //根据抽奖id进行装配抽奖策略
    @Override
    public boolean assembleLotteryStrategy(Long strategyId) {
        //1.查询策略配置
        List<StrategyAwardEntity> strategyAwardEntities = repository.queryStrategyAwardList(strategyId);
        //2.缓存奖品库存(用于decr扣减库存使用)
        for (StrategyAwardEntity strategyAward : strategyAwardEntities) {
            Integer awardId = strategyAward.getAwardId();
            Integer awardCount = strategyAward.getAwardCount();
            cacheStrategyAwardCount(strategyId, awardId, awardCount);
        }

        //3.1 默认装配规则
        assembleLotteryStrategy(String.valueOf(strategyId), strategyAwardEntities);
        //3.2权重配置 - 实用于rule_weight 权重规则配置
        StrategyEntity strategyEntity = repository.queryStrategyEntityByStrategyId(strategyId);
        String ruleWeight = strategyEntity.getRuleWeight();
        if (ruleWeight == null) return true;
        StrategyRuleEntity strategyRuleEntity = repository.queryStrategyRule(strategyId, ruleWeight);
        if (strategyRuleEntity == null) {
            throw new AppException(ResponseCode.STRATEGY_RULE_WEIGHT_IS_NULL.getCode(), ResponseCode.STRATEGY_RULE_WEIGHT_IS_NULL.getInfo());
        }
        Map<String, List<Integer>> ruleWeightValueMap = strategyRuleEntity.getRuleWeightValues();
        //keySet将map中的key都保存起来了
        Set<String> keys = ruleWeightValueMap.keySet();
        for (String key : keys) {
            List<Integer> ruleWeightValues = ruleWeightValueMap.get(key);
            //列表复制
            ArrayList<StrategyAwardEntity> strategyAwardEntitiesClone = new ArrayList<>(strategyAwardEntities);
            //lamb表达式 它遍历了 strategyAwardEntitiesClone 列表中的每个元素，并检查该元素的 AwardId 是否存在于 ruleWeightValues
            strategyAwardEntitiesClone.removeIf(entity -> !ruleWeightValues.contains(entity.getAwardId()));
            assembleLotteryStrategy(String.valueOf(strategyId).concat("_").concat(key), strategyAwardEntitiesClone);
        }
        return true;
    }



    //用于获取奖品值的概率于奖品表
    public void assembleLotteryStrategy(String key, List<StrategyAwardEntity> strategyAwardEntities) {
        //1.获取最小概率值
        BigDecimal minAwardRate = strategyAwardEntities.stream()
                .map(StrategyAwardEntity::getAwardRate)
                .min(BigDecimal::compareTo)
                .orElse(BigDecimal.ZERO);

        //2.获取概率值总和 reduce是一种归约， 前面是初值0 后面是进行相加
        BigDecimal totalAwardRate = strategyAwardEntities.stream()
                .map(StrategyAwardEntity::getAwardRate)
                .reduce(BigDecimal.ZERO, BigDecimal::add);

        //3.用1除以0.0001获取概率范围
        //其中divide是除法计算 0代表了精度 RoundingMode表示向上取整
        BigDecimal rateRange = totalAwardRate.divide(minAwardRate, 0, RoundingMode.CEILING);

        //4、建立起一个strategyAwardSearchRateTables，比如有3个奖品，1奖品概率为70% 2奖品概率为20% 3为10% 这个表存储的为1111111223
        ArrayList<Integer> strategyAwardSearchRateTables = new ArrayList<>(rateRange.intValue());
        for (StrategyAwardEntity strategyAward : strategyAwardEntities) {
            Integer awradId = strategyAward.getAwardId();
            BigDecimal awardRate = strategyAward.getAwardRate();
            //计算出，而讴歌概率值需要存放到查找表的数量，循环填充
            //setScale是将其设置小数位数和舍入模式
            //这里为什么向上舍入后还要跟一个intValue 因为i是int值 我们setScale后仍旧是BigDecimal
            for (int i = 0; i < rateRange.multiply(awardRate).setScale(0, RoundingMode.CEILING).intValue(); i++) {
                strategyAwardSearchRateTables.add(awradId);
            }
        }

        //5.乱序
        Collections.shuffle(strategyAwardSearchRateTables);

        /*
        6生成map 空间换时间
        营销服务 第3节：策略概率装配处理的重点
        如果是生产random 比如 A奖是0-10 B是10-20 以此类推 我们在这个strategyAwardSearchRateTables找时候肯定要用for 现在用map直接进行索引
        */
        Map<Integer, Integer> shuffleStrategyAwardSearchRateTables = new LinkedHashMap<>();
        for (int i = 0; i < strategyAwardSearchRateTables.size(); i++) {
            shuffleStrategyAwardSearchRateTables.put(i, strategyAwardSearchRateTables.get(i));
        }

        //7.存储redis
        //这里因为是面向分布式的应用，所以把rateRange也一起存储了
        repository.storeStrategyAwardSearchRateTable(key, shuffleStrategyAwardSearchRateTables.size(), shuffleStrategyAwardSearchRateTables);
    }

    @Override
    public Integer getRandomAwardId(Long strategyId) {
        int rateRange = repository.getRateRange(strategyId);
        //安全性较高的随机数生成
        return repository.getStrategyAwardAssemble(String.valueOf(strategyId), new SecureRandom().nextInt(rateRange));
    }

    @Override
    public Integer getRandomAwardId(Long strategyId, String ruleWeighValue) {
        String key = String.valueOf(strategyId).concat("_").concat(ruleWeighValue);
        int rateRange = repository.getRateRange(key);
        return repository.getStrategyAwardAssemble(key, new SecureRandom().nextInt(rateRange));
    }


    @Override
    public Integer getRandomAwardId(String key) {
        int rateRange = repository.getRateRange(key);
        return repository.getStrategyAwardAssemble(key, new SecureRandom().nextInt(rateRange));
    }

    private void cacheStrategyAwardCount(Long strategyId, Integer awardId, Integer awardCount) {
        String cacheKey = Constants.RedisKey.STRATEGY_AWARD_COUNT_KEY + strategyId + Constants.UNDERLINE + awardId;
        repository.cacheStrategyAwardCount(cacheKey, awardCount);
    }

    @Override
    public Boolean subtractionAwardStock(Long strategyId, Integer awardId) {
        String cacheKey = Constants.RedisKey.STRATEGY_AWARD_COUNT_KEY + strategyId + Constants.UNDERLINE + awardId;
        return repository.subtractionAwardStock(cacheKey);
    }
}
